package mc

import (
	"fmt"
	"reflect"
	"strings"
)

// 模型配置
type modelConfig struct {
	Table         tableConfig            `json:"table"`  // 数据表信息
	Tree          *treeConfig            `json:"tree"`   // 树形结构
	Kvs           map[string]kvConfig    `json:"kvs"`    // 键值对配置结构
	Enums         map[string][][]string  `json:"enums"`  // 枚举列表
	Fields        []*fieldConfig         `json:"fields"` // 基础字段列表
	Pages         map[string]pageConfig  `json:"pages"`  // 页面
	Extra         map[string]interface{} `json:"extra"`  // 扩展属性
	fieldIndexes  map[string]int         `json:"-"`      // 字段索引
	rowAuthFields []string               `json:"-"`      // 所有受行权限控制的字段列表
}

// 数据表配置信息
type tableConfig struct {
	Name         string        `json:"name"`                        // 表名, 默认为模型名
	ConnName     string        `json:"conn_name" default:"default"` // 数据库连接名   默认 default
	DBName       string        `json:"db_name"`                     // 数据库名  默认 数据库连接配置中的数据库名
	Alias        string        `json:"alias"`                       // 表别名   默认 表名
	Order        string        `json:"order"`                       // 默认排序  选填
	Pk           string        `json:"pk" default:"id"`             // 主键字段名 默认 id
	AutoInc      bool          `json:"auto_inc" default:"true"`     // 主键自增长 默认 true
	UniqueFields []string      `json:"unique_fields"`               // 唯一性字段列表  选填
	Where        []interface{} `json:"where"`                       // 基础查询条件   默认""
	Joins        []string      `json:"joins"`                       // 外联SQL
	Groups       []string      `json:"groups"`                      // 分组SQL
}

// 树形结构配置信息
type treeConfig struct {
	PathBit   int    `json:"path_bit" default:"2"`       // 树型结构路径每层位数  默认 2
	PathField string `json:"path_field" default:"path"`  // 树型结构的路径字段   默认 path
	NameField string `json:"name_field" default:"title"` // 树形结构的节点名称字段 默认title
}

// 单个键值对配置信息
type kvConfig struct {
	Items            []string `json:"items"`                       // 使用的字段名集
	KeyFields        []string `json:"key_fields"`                  // 主键（必填）
	ValueFields      []string `json:"value_fields"`                // 值字段 (必填）
	ExtraFields      []string `json:"extra_fields"`                // 其它附加字段
	NullFilterFields []string `json:"null_filter_fields"`          // 空值过滤字段
	Where            string   `json:"where"`                       // 查询条件（只作用此kv选择中)
	Joins            []string `json:"joins"`                       // 外联关系
	Order            string   `json:"order"`                       // 排序
	TreeIndent       string   `json:"tree_indent"`                 // 树型模型节点名称前根据层级加前缀字符
	KeyConnector     string   `json:"key_connector" default:"-"`   // key各字符连接符 (默认-)
	ValueConnector   string   `json:"value_connector" default:"-"` // value各字段连接符(默认-)
}

// 单个字段配置信息
type fieldConfig struct {
	Name         string                 `json:"name"`                  // 字段名
	Title        string                 `json:"title"`                 // （基础）标题 (默认为name值）
	Alias        string                 `json:"alias"`                 // （基础）别名，与SQL中刚好相反，如SQL中：SUM(money) AS total，则此处填写sum(abc)，total为Column单项的key（默认为""）
	Default      interface{}            `json:"default"`               // （基础）默认值（默认""）
	Multiple     bool                   `json:"multiple"`              // （基础）是否支持多选（默认false）
	Separator    string                 `json:"separator" default:","` // （基础）多选或范围值的分隔符，尽量使用默认值逗号，因为mysql的MATCH AGAINST只使用逗号分隔，除非你的业务中不使用些语句（默认逗号,)
	From         string                 `json:"from"`                  // （基础）指定字段数据来源,当来源为enum时，值为enum_name ，当来源为kvs时，值为：model_name:kv_name
	TimeFormat   string                 `json:"time_format"`           // （基础）日期时间格式化
	Format       string                 `json:"format"`                // （基础）输出格式化 （和TimeFormat只能使用一个)
	NoSQL        bool                   `json:"no_sql"`                // （基础）数据字段不是数据库字段
	Disabled     bool                   `json:"disabled"`              // （编辑表单）是否禁止编辑 默认false，对于pk字段，会自动判断
	Hidden       bool                   `json:"hidden"`                // （编辑表单）表示是否隐藏 默认false
	Readonly     bool                   `json:"readonly"`              // （编辑表单）表示是否只读 默认false
	Widget       string                 `json:"widget"`                // （编辑表单）字段渲染类型，默认text
	CssClass     string                 `json:"css_class"`             // （编辑表单）样式
	CssStyle     string                 `json:"css_style"`             // （编辑表单）样式属性
	Placeholder  string                 `json:"placeholder"`           // （编辑表单）占位文本
	Info         string                 `json:"info"`                  // （编辑表单）字段说明（默认""）
	Verify       string                 `json:"verify"`                // （编辑表单）后端验证规则
	UIVerify     string                 `json:"ui_verify"`             //  (编辑表单）前端验证规则
	Width        int                    `json:"width" default:"120"`   // （列表）显示宽度（默认100）
	Align        string                 `json:"align" default:"left"`  // （列表）列表时对齐方式
	Sort         bool                   `json:"sort" default:"true"`   // （列表）列表时是否允许排序（默认false)
	Fixed        string                 `json:"fixed"`                 // （列表）固定
	Count        string                 `json:"count"`                 // （查询）此字段表尾汇总SQL，如SUM(money)，为""，则此字段不汇总
	Between      bool                   `json:"between"`               // （查询）是否两值取范围
	Where        string                 `json:"where"`                 // （查询）查询时的条件
	SearchValues []string               `json:"search_values"`         // （查询）查询时的条件值，默认直接[]
	Extra        map[string]interface{} `json:"extra"`                 // （扩展）自定义扩展信息
	FromInfo     *FromInfo              `json:"-"`                     // 字段来源信息
}

// 模型页面配置信息
type pageConfig struct {
	Name           string                 `json:"-"`             // 页面名称
	Items          []string               `json:"items"`         // 使用的字段名集
	ExcludeItems   []string               `json:"exclude_items"` // 排除的字段名集（如果items不为空，此项不起作用）
	Fields         []*fieldConfig         `json:"fields"`        // 字段信息集
	SearchPageName string                 `json:"search"`        // 查询page key
	List           pageListConfig         `json:"list"`          // 列表信息
	Javascript     pageJsConfig           `json:"javascript"`    // 页面js回调
	Extra          map[string]interface{} `json:"extra"`         // 页面扩展配置信息
	Disabled       bool                   `json:"disabled"`      // 此页面是否禁用
	UseAction      string                 `json:"use_action"`    // 使用控制器操作方法名
	SearchHidden   bool                   `json:"search_hidden"` // 是否隐藏（仅对查询项页有效）默认false
	fieldIndexes   map[string]int         `json:"-"`             // 字段索引
}

// 模型列表属性
type pageListConfig struct {
	Single    bool   `json:"single"`                  // 列表是否单选   默认 false
	Number    bool   `json:"number"`                  // 列表显示序号  默认 false
	IsCount   bool   `json:"is_count"`                // 是否有合计表尾
	CountText string `json:"count_text" default:"合计"` // 表尾文本
}

// 回调js配置属性
type pageJsConfig struct {
	RenderBefore  string `json:"render_before"`  // 页面渲染前回调
	RenderAfter   string `json:"render_after"`   //页面渲染后回调
	SubmitBefore  string `json:"submit_before"`  // 页面提交前回调
	SubmitSuccess string `json:"submit_success"` // 页面提交后回调
	SubmitError   string `json:"submit_error"`
}

func (config *modelConfig) IsTree() bool {
	if config.Tree != nil {
		return true
	}
	return false
}

// 分析配置信息
func (config *modelConfig) parse(modelName string) {
	// 如果没有指定数据连接，则使用默认
	if config.Table.ConnName == "" {
		config.Table.ConnName = option.DefaultConnName
	}
	// 如果没有指定表名，使用模型配制名称
	if config.Table.Name == "" {
		config.Table.Name = modelName
	}
	// 如果表没指定别名，就直接使用表名作别名
	if config.Table.Alias == "" {
		config.Table.Alias = config.Table.Name
	}
	// 没有指定主键，则使用默认id
	if config.Table.Pk == "" {
		config.Table.Pk = "id"
	}
	// 如果是树形结构时，使用路径字段进行排序
	if config.IsTree() && config.Table.Order == "" {
		config.Table.Order = fmt.Sprintf("%s asc", config.Tree.PathField)
	}
	config.rowAuthFields = make([]string, 0)
	config.fieldIndexes = make(map[string]int)
	for i, _ := range config.Fields {
		field := config.Fields[i]
		field.parse(config)
		//字段索引
		config.fieldIndexes[field.Name] = i
		if field.FromInfo != nil && field.FromInfo.IsRowAuth {
			config.rowAuthFields = append(config.rowAuthFields, field.Name)
		}
	}
}

// 分析页面配置信息
func (pc *pageConfig) parse(m *Model, pageName string) {
	pc.Name = pageName
	if pc.UseAction == "" {
		pc.UseAction = CamelToCase(pc.Name)
	}else{
		pc.UseAction = CamelToCase(pc.UseAction)
	}
	//页面字义的字段索引
	pc.fieldIndexes = make(map[string]int)
	for i, field := range pc.Fields {
		pc.fieldIndexes[field.Name] = i
	}

	//如果没有指定字段列表，则使用模型字段列表
	if pc.Items == nil || len(pc.Items) <= 0 || pc.Items[0] == "*" {
		pc.Items = make([]string, 0)
		for _, field := range m.config.Fields {
			pc.Items = append(pc.Items, field.Name)
		}
		for _, field := range pc.Fields {
			if !InArray(field.Name, pc.Items) {
				pc.Items = append(pc.Items, field.Name)
			}
		}
	}

	// 分析各个字段
	for _, fieldName := range pc.Items {
		// 合并字段信息
		var baseField *fieldConfig
		var pageField *fieldConfig

		if index, ok := m.config.fieldIndexes[fieldName]; ok {
			baseField = m.config.Fields[index]
		}
		if index, ok := pc.fieldIndexes[fieldName]; ok {
			pageField = pc.Fields[index]
		}
		if pageField == nil {
			if baseField == nil {
				panic(ErrorInternalServerError.Copy("%s 模型 %s 页面未配置 %s 字段信息", m.name, pageName, fieldName))
			}
			pc.Fields = append(pc.Fields, baseField)
		} else {
			if baseField == nil {
				pageField.parse(m.config)
			} else {
				if err := mergeConfigField(pageField, baseField); err != nil {
					panic(ErrorInternalServerError.Copy("%s 模型 %s 页面 %s 字段信息配置信息错误：%s ", m.name, pageName, fieldName, err.Error()))
				}
				pageField.parse(m.config)
			}
		}
	}

	//重新刷新页面字义的字段索引
	pc.fieldIndexes = make(map[string]int)
	for i, field := range pc.Fields {
		pc.fieldIndexes[field.Name] = i
	}
}

// 分析字段配置信息
func (field *fieldConfig) parse(config *modelConfig) {
	if field.Title == "" {
		field.Title = field.Name
	}
	// 条件信息
	if field.Where != "" && field.SearchValues == nil {
		field.SearchValues = []string{"?"}
	}
	// 扩展信息
	if field.Extra == nil {
		field.Extra = make(map[string]interface{})
	}

	// 树形结构，各字段不允许排序
	if config.IsTree() {
		field.Sort = false
	}
	// 当字段是主键时，且是自增表时，pk字段不能编辑
	if field.Name == config.Table.Pk {
		if config.Table.AutoInc {
			field.Disabled = true
		} else {
			if field.Verify == "" {
				field.Verify = "required"
			}
			if field.UIVerify == "" {
				field.UIVerify = "required"
			}
		}
		// f.Required = !m.AutoInc
	}
	// 不允许编辑项，不进行验证
	if field.Disabled {
		field.Verify = ""
		field.UIVerify = ""
	}
	// 日期时间格式
	if field.TimeFormat != "" && InArray(field.TimeFormat, option.TimeFormats) {
		field.TimeFormat = option.TimeFormats[field.TimeFormat] //"datetime"
	}
	// 分析数据来源
	field.FromInfo = ParseFromInfo(field.From)
}

// 合并两个字段信息
func mergeConfigField(cField1 *fieldConfig, cField2 *fieldConfig) error {
	if cField1 == nil && cField2 == nil {
		return fmt.Errorf("未配置字段信息")
	}
	if cField1 == nil {
		cField1 = &fieldConfig{}
	}
	if cField2 == nil {
		return nil
	}
	v1 := reflect.ValueOf(*cField1)           // 初始化为field1保管的具体值的v1
	v2 := reflect.ValueOf(*cField2)           // 初始化为field2保管的具体值的v2
	v1Elem := reflect.ValueOf(cField1).Elem() // 返回 field1 指针保管的值
	for i := 0; i < v1Elem.NumField(); i++ {
		field1 := v1.Field(i) // 返回结构体的第i个字段
		field2 := v2.Field(i) // 返回结构体的第i个字段
		// field.Interface() 当前持有的值
		// reflect.Zero 根据类型获取对应的 零值
		// 这个必须调用 Interface 方法 否则为 reflect.Value 构造体的对比 而不是两个值的对比
		// 这个地方不要用等号去对比 因为golang 切片类型是不支持 对比的
		if reflect.DeepEqual(field1.Interface(), reflect.Zero(field1.Type()).Interface()) { // 如果第一个构造体某个字段对应类型的默认值
			if !reflect.DeepEqual(field2.Interface(), reflect.Zero(field2.Type()).Interface()) { // 如果第二个构造体 这个字段不为空
				if v1Elem.Field(i).CanSet() != true { // 如果不可以设置值 直接返回
					return fmt.Errorf("not set value")
				}
				v1Elem.Field(i).Set(field2) // 设置值
			}
		}
	}
	return nil
}

// 分析字段来源信息
// @param from 来源配置字符串
// @return fromInfo 来源信息
func ParseFromInfo(from string) (fromInfo *FromInfo) {
	if from == "" {
		return
	}
	fromInfo = &FromInfo{}
	fromInfo.IsKv = strings.Contains(from, ":")
	if fromInfo.IsKv { // 外部关联表kv
		f := strings.Split(from, ":")
		fromInfo.FromName = f[0]
		if len(f) < 2 || f[1] == "" { // 如果没有指定kvName，使用默认kvName
			f[1] = DefaultKvItemName
		}
		fromInfo.FromName, fromInfo.KvItemName = f[0], f[1]
		if InArray(fromInfo.FromName, option.ModelAuth.GetRowAuthModelsCallback()) { // 受行权限控制的字段
			fromInfo.IsRowAuth = true
		}
	} else { // enum
		fromInfo.FromName = from
	}
	return
}
